The date, time, datetime2, and datetimeoffset types are four date and time data types that were introduced in SQL Server 2008. These types should be used for all new database development in lieu of the traditional datetime and smalldatetime data types. The newer types are aligned with the .NET Framework, Microsoft Windows, and the SQL standard—unlike datetime and smalldatetime—and there are improvements in range, precision, and storage as well.
Separation of Dates and Times
We’ll begin by looking at the separate date and time types. If you need to store only a date value (for example, a date of birth), use the date type. Similarly, use the time type for storing just a time value (for example, a daily medication time), as shown here:
DECLARE @DOB date
DECLARE @MedsAt time
The datetime and smalldatetime
types, which were the only previously available options, each include
both a date and a time portion. In cases where only the date or only the
time is needed, the extraneous portion consumes storage needlessly,
which results in wasted space in the database. In addition to saving
storage, using date rather than datetime
yields better performance for date-only manipulations and calculations,
because there is no time portion to be handled or considered. And the time type is provided for those less common cases when you require a time without a date; for example, feeding time at the zoo.
More Portable Dates and Times
To store both a date and a time as a single value, use the datetime2 data type. This data type supports the same range of values as the DateTime data type in the .NET Framework, so it can store dates from 1/1/0001 (DateTime.MinValue in .NET) to 12/31/9999 (DateTime.MaxValue in .NET) in the Gregorian calendar. Contrast this with the allowable date values for the old datetime
type, which range only from 1/1/1753 to 12/31/9999. This means that
dates in .NET from 1/1/0001 through 12/31/1752 can’t be stored at all in
SQL Server’s datetime type, a problem solved by using either the date, datetime2, or datetimeoffset
type. Because the supported range of dates is the same in both .NET and
SQL Server, any date can be safely passed between these client and
server platforms with no concern. You are strongly encouraged to
discontinue using the older datetime and smalldatetime data types and to use only date and datetime2 types for new development (or the new datetimeoffset type for time zone awareness, which is discussed next).
There has also been a need for greater precision of fractional seconds in time values. The datetime
type is accurate only within roughly 3.33 milliseconds, whereas time
values in Windows and .NET have a significantly greater 100-nanosecond
(10-millionth of a second) accuracy. (The smalldatetime
type doesn’t even support seconds and is accurate only to the minute.)
Storing times in the database therefore results in a loss of precision.
Like the expanded range of supported dates, the new time, datetime2, and datetimeoffset
types are now more aligned with .NET and other platforms by also
providing the same 100 nanosecond accuracy. As a result, you no longer
incur any data loss of fractional second accuracy between platforms when
recording time values to the database. Of course, there are storage
implications that come with greater time precision, and we’ll discuss
those momentarily.
The fourth and last data type in this category is datetimeoffset. This type defines a date and time with the same range and precision that datetime2
provides but also includes an offset value with a range of –14:00 to
+14:00 that identifies the time zone. In the past, the only practical
approach for globalization of dates and times in the database has been
to store them in Coordinated Universal Time (UTC) format. Doing this
requires back-and-forth conversion between UTC and local time that must
be handled at the application level, and that means writing code.
Using the datetimeoffset
type, you can store values that represent the local date and time in
different regions of the world and include the appropriate time zone
offset for the region in each value. Because the time zone offset
embedded in the date and time value is specific to a particular locale, SQL
Server is able to perform date and time comparisons between different
locales without any conversion efforts required on your part. Although datetimeoffset
values appear to go in and come out as dates and times local to a
particular region, they are internally converted, stored, and treated in
UTC format for comparisons, sorting, and indexing, while the time zone
“tags along.”
Calculations and comparisons are therefore performed
correctly and consistently across all dates and times in the database
regardless of the different time zones in various regions. By simply
appending the time zone offset to datetimeoffset
values, SQL Server handles the conversions to and from UTC for you
automatically in the background. Even better, you can obtain a datetimeoffset
value either as UTC or local time. For those of you building databases
that need to store various local times (or even just dates) in different
regions of the world, this is an extremely convenient feature. The
database handles all the details, so the application developer doesn’t
have to. Time zone functionality is simply available for free right at
the database level.
For example, the database knows that 9:15 AM
in New York is in fact later than 10:30 AM in Los Angeles if you store
the values in a datetimeoffset
data type with appropriate time zone offsets. Because the New York time
specifies a time zone offset of –5:00 and the Los Angeles time has an
offset of –8:00, SQL Server is aware of the three-hour difference
between the two time zones and accounts for that difference in all date/time manipulations and calculations. This behavior is demonstrated by the code in Example 1.
Example 1. Time zone calculations using datetimeoffset.
DECLARE @Time1 datetimeoffset
DECLARE @Time2 datetimeoffset
DECLARE @MinutesDiff int
SET @Time1 = '2012-02-10 09:15:00-05:00
' -- NY time is UTC -05:00
SET @Time2 = '2012-02-10 10:30:00-08:00'
-- LA time is UTC -08:00
SET @MinutesDiff = DATEDIFF(minute, @Time1, @Time2)
SELECT @MinutesDiff
If you run this code, you will see that the DATEDIFF calculation returns 255. This indicates that SQL
Server is clearly able to account for the three-hour difference in the
time zones. Because 10:30 AM in Los Angeles is actually 1:30 PM in New
York, a difference of 255 minutes (4 hours and 15 minutes) between that
time and 9:15 AM New York time was calculated correctly.
Note
Time zone names
are not supported, nor is there support for daylight savings time.
Unfortunately, these features did not make it into the final release of
the product, but they are on the list for the next version of SQL
Server. Time zones can be expressed only by hour/minute offsets, and you
must continue to handle daylight savings time considerations on your
own.
Date and Time Accuracy, Storage, and Format
Date values stored in date, datetime2, and datetimeoffset types are compacted into a fixed storage
space of 3 bytes. They use an optimized format that is 1 byte less than
the 4 bytes consumed by the date portion of the older datetime type (supporting a greater range in a smaller space).
Time values stored in time, datetime2, and datetimeoffset
types, by default, consume 5 bytes of storage to support the same
100-nanosecond accuracy as Windows and .NET. However, you can specify a
lower degree of precision for even more compacted storage by providing
an optional scale parameter when declaring time, datetime2, and datetimeoffset
variables. The scale can range from 0 to 7, with 0 offering no
fractional-second precision at all consuming 3 bytes, and 7 (the
default) offering the greatest fractional-second precision (100
nanoseconds) consuming 5 bytes. The scale essentially dictates the
number of digits supported after the decimal point of the seconds value,
where a scale value of 7 supports a fractional precision of 100
nanoseconds (each 100 nanoseconds being 0.0000001 second).
The
default scale is 7, which offers the greatest precision (to 100
nanoseconds) in the largest space (5 bytes). This means that declaring a
variable as time, datetime2, or datetimeoffset is the same as declaring it as time(7), datetime2(7), or datetimeoffset(7), making the following two statements equivalent.
DECLARE @StartDateTime datetime2
DECLARE @StartDateTime datetime2(7)
If you don’t require any
fractional precision at all, use a scale of 0, as in the following
statement, which consumes only 3 bytes to store a time in @FeedingTime.
DECLARE @FeedingTime time(0)
Two time values with differing scales are perfectly compatible with each other for comparison. SQL
Server automatically converts the value with the lower scale to match
the value with the greater scale and compares the two safely.
Virtually
all industry-standard string literal formats are supported for
conveniently representing dates and times. For example, the date May 15, 2012, can be expressed in any of the formats shown in Table 1.
Table 1. Common valid date and time string literal formats
Format | Example |
---|
Numeric | 5/15/2012, 15-05-2012, 05.15.2012 |
Alphabetical | May 15, 2012 |
ISO8601 | 2012-05-15, 201205153 |
ODBC | {d’2012-05-15’} |
W3C XML | 2012-05-15Z |
You
have similar flexibility for representing times. For example, the same
time value can be expressed as 23:30, 23:30 :00, 23:30:00.0000, or
11:30:00 PM. Time zone offsets are expressed merely by appending a plus
or minus sign followed by the UTC hours and minutes for the zone—for
example, +02:00 for Jerusalem.
You can use CAST or CONVERT to extract just the date or time portions of a datetime2 column for searching. When you perform such a conversion on a datetime2
column that is indexed, SQL Server does not need to resort to a
sequential table scan and is able to perform the much faster index seek
to locate the specific date or time. For example, the following code
defines a table with a datetime2 type that has a clustered index. Selecting by date or time only can be achieved using CONVERT, while still using the clustered index for efficient searching, as shown in Example 2.
Example 2. Using CONVERT to extract the date and time portion from a datetime2 column.
CREATE TABLE DateList(MyDate datetime2)
CREATE CLUSTERED INDEX idx1 ON DateList(MyDate)
-- Insert some rows into DateList
INSERT INTO DateList VALUES
('2011-10-10 12:15:00'),
('2012-04-07 09:00:00'),
('2012-04-07 10:00:00'),
('2011-10-10 09:00:00')
SELECT MyDate FROM DateList WHERE MyDate = '2011-10-10 12:15:00'
SELECT MyDate FROM DateList WHERE CONVERT(time(0), MyDate) = '09:00:00'
SELECT MyDate FROM DateList WHERE CONVERT(date, MyDate) = '2012-04-07'
If you request the estimated execution plan for this code, you will see that the clustered index is leveraged when using CONVERT to query either the date or time portion of the indexed datetime2 column.